﻿using CLROBS;
using DesertKharonIrcPlugin.Emotes;
using DesertKharonIrcPlugin.Emotes.Twitch;
using DesertKharonIrcPlugin.GdiPlusRendering;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DesertKharonIrcPlugin
{
    public class Renderer : IDisposable
    {
        private object _lock = new object();

        private DesertKharonIrcPluginSettings _settings;

        private int _width = 0;
        private int _height = 0;

        // OBS
        private GraphicsSystem _obsGraphicsSystem;
        private Texture _texture;
        private bool _writeTexture = true;

        // GDI+
        private Graphics _graphics;
        private Bitmap _bitmap;
        private BitmapData _bitmapData;
        private byte[] _bitmapBytes;
        private Rectangle _fullBitmapRect;

        // Fonts
        private FontRenderer _universalFontRenderer;
        private FontRenderer _broadcasterNicknameFontRenderer;
        private FontRenderer _broadcasterMessageFontRenderer;
        private FontRenderer _moderatorNicknameFontRenderer;
        private FontRenderer _moderatorMessageFontRenderer;
        private FontRenderer _friendNicknameFontRenderer;
        private FontRenderer _friendMessageFontRenderer;
        private FontRenderer _foeNicknameFontRenderer;
        private FontRenderer _foeMessageFontRenderer;
        private FontRenderer _userNicknameFontRenderer;
        private FontRenderer _userMessageFontRenderer;

        // Messages
        private List<DrawableBase> _drawableList = new List<DrawableBase>(4096);
        private float _textHeight = 0f;
        private float _spaceWidth = 0f;

        private EmoteManager _emoteManager;

        public Texture Texture
        {
            get 
            {
                if (_writeTexture)
                    WriteTexture();

                return _texture; 
            }
        }

        private Color _clearColor = Color.Transparent;
        public Color ClearColor
        {
            get { return _clearColor; }
            set { _clearColor = value; }
        }

        public Renderer(GraphicsSystem obsGraphicsSystem, DesertKharonIrcPluginSettings settings)
        {
            if (obsGraphicsSystem == null)
                throw new ArgumentNullException("obsGraphicsSystem", "This should be set.");

            _obsGraphicsSystem = obsGraphicsSystem;
            _settings = settings;

            _textHeight = "a".Measure(_settings.Font).Height;
            _spaceWidth = " ".Measure(_settings.Font).Width * 0.333333f;

            _emoteManager = new EmoteManager(DesertKharonIrcPlugin.GetEmoteFolder(), _textHeight / 32f, TwitchEmoteParser.ParseAll());

            Resize(16, 16);

            CreateFontRenderers();
        }

        private void CreateFontRenderers()
        {
            CreateUniversalFontRenderer();

            CreateBroadcasterNickameFontRenderer();
            CreateBroadcasterMessageFontRenderer();

            CreateModeratorNicknameFontRenderer();
            CreateModeratorMessageFontRenderer();

            CreateFriendNicknameFontRenderer();
            CreateFriendMessageFontRenderer();

            CreateFoeNicknameFontRenderer();
            CreateFoeMessageFontRenderer();

            CreateUserNicknameFontRenderer();
            CreateUserMessageFontRenderer();
        }

        private void CreateUniversalFontRenderer()
        {
            _universalFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.FontColor,
                OutlineColor = _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateBroadcasterNickameFontRenderer()
        {
            _broadcasterNicknameFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.BroadcasterNicknameFontColor,
                OutlineColor = _settings.DifferentBroadcasterNicknameOutline ? _settings.BroadcasterNicknameFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateBroadcasterMessageFontRenderer()
        {
            _broadcasterMessageFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.BroadcasterMessageFontColor,
                OutlineColor = _settings.DifferentBroadcasterMessageOutline ? _settings.BroadcasterMessageFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateModeratorNicknameFontRenderer()
        {
            _moderatorNicknameFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.ModeratorNicknameFontColor,
                OutlineColor = _settings.DifferentModeratorNicknameOutline ? _settings.ModeratorNicknameFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateModeratorMessageFontRenderer()
        {
            _moderatorMessageFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.ModeratorMessageFontColor,
                OutlineColor = _settings.DifferentModeratorMessageOutline ? _settings.ModeratorMessageFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateFriendNicknameFontRenderer()
        {
            _friendNicknameFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.FriendNicknameFontColor,
                OutlineColor = _settings.DifferentFriendNicknameOutline ? _settings.FriendNicknameFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateFriendMessageFontRenderer()
        {
            _friendMessageFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.FriendMessageFontColor,
                OutlineColor = _settings.DifferentFriendMessageOutline ? _settings.FriendMessageFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateFoeNicknameFontRenderer()
        {
            _foeNicknameFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.FoeNicknameFontColor,
                OutlineColor = _settings.DifferentFoeNicknameOutline ? _settings.FoeNicknameFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateFoeMessageFontRenderer()
        {
            _foeMessageFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.FoeMessageFontColor,
                OutlineColor = _settings.DifferentFoeMessageOutline ? _settings.FoeMessageFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateUserNicknameFontRenderer()
        {
            _userNicknameFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.UserNicknameFontColor,
                OutlineColor = _settings.DifferentUserNicknameOutline ? _settings.UserNicknameFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void CreateUserMessageFontRenderer()
        {
            _userMessageFontRenderer = new FontRenderer(_settings.Font)
            {
                BackgroundColor = _settings.BackgroundColor,
                FontColor = _settings.UserMessageFontColor,
                OutlineColor = _settings.DifferentUserMessageOutline ? _settings.UserMessageFontOutlineColor : _settings.FontOutlineColor,
                OutlineThickness = _settings.FontOutlineThickness,
                DrawOutline = _settings.FontOutline,
                Opacity = _settings.Opacity
            };
        }

        private void Resize(int width, int height)
        {
            if (width != _width || height != _height)
            {
                _width = width;
                _height = height;

                if (_texture != null)
                    _texture.Dispose();

                if (_graphics != null)
                    _graphics.Dispose();

                if (_bitmap != null)
                    _bitmap.Dispose();

                _fullBitmapRect = new Rectangle(0, 0, _width, _height);
                _bitmap = new Bitmap(_width, _height);
                _bitmapBytes = new byte[_width * _height * 4];
                _graphics = Graphics.FromImage(_bitmap);
                _graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                _graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                _graphics.Clear(_clearColor);

                _texture = _obsGraphicsSystem.CreateTexture((uint)_width, (uint)_height, GSColorFormat.GS_BGRA, null, false, false);
            }
        }

        private string _lastMessageNick = "";
        public void AddMessage(IrcManagerMessage message)
        {
            if (String.IsNullOrWhiteSpace(message.Message) == false)
            {
                DrawableBase nicknameDrawable = CreateNicknameDrawable(message, _lastMessageNick == message.NickName);
                if (nicknameDrawable != null)
                {
                    List<DrawableBase> drawableMessagePartList = CreateMessageDrawables(message);
                    if (drawableMessagePartList != null && drawableMessagePartList.Count > 0)
                    {
                        lock (_lock)
                        {
                            _drawableList.Add(nicknameDrawable);
                            _drawableList.AddRange(drawableMessagePartList);
                        }
                    }
                }
            }

            _lastMessageNick = message.NickName;
        }

        private List<DrawableBase> CreateMessageDrawables(IrcManagerMessage message)
        {
            string messageText = FormatMessage(message);
            if (!String.IsNullOrWhiteSpace(messageText))
            {
                List<string> messageWordList = _emoteManager.SplitMessage(messageText, StringSplitOptions.RemoveEmptyEntries);
                if (messageWordList != null && messageWordList.Count > 0)
                {
                    List<DrawableBase> drawableList = messageWordList
                        .Select(e => CreateMessagePartDrawable(message, e))
                        .Where(e => e != null)
                        .ToList();

                    DrawableBase last = drawableList.LastOrDefault();
                    if (last != null)
                        last.NewLine = true;

                    return drawableList;
                }
            }

            return null;
        }

        private DrawableBase CreateMessagePartDrawable(IrcManagerMessage message, string word)
        {
            Emote emote = _emoteManager.GetEmote(word);
            if (emote != null)
                return new DrawableEmote(emote, message.NickName);

            Bitmap messageWordBitmap = GetMessageFontRenderer(message.Source).Draw(word);
            if (messageWordBitmap != null)
                return new DrawableBitmap(messageWordBitmap, true, message.NickName);

            return null;
        }

        private DrawableBase CreateNicknameDrawable(IrcManagerMessage message, bool followupMessage)
        {
            FontRenderer nicknameRenderer = GetNickNameFontRenderer(message.Source);
            Bitmap nicknameBitmap = null;

            if (_settings.LoginPreset == LoginPreset.Twitch && _settings.TwitchColors)
                nicknameBitmap = nicknameRenderer.Draw(FormatNickName(message, followupMessage), message.UserColor);
            else
                nicknameBitmap = nicknameRenderer.Draw(FormatNickName(message, followupMessage));

            if (nicknameBitmap != null)
                return new DrawableBitmap(nicknameBitmap, true, message.NickName);
            
            return null;
        }

        public void ClearMessagesFromNick(string nickname)
        {
            lock (_lock)
            {
                _drawableList = _drawableList.Where(textPart => textPart.AuthorNickname != nickname).ToList();
            }
        }

        private string FormatNickName(IrcManagerMessage message, bool followupMessage)
        {
            string nickNameFormat = !followupMessage ? _settings.NickNameFormat : _settings.FollowupNickNameFormat;
            string timeFormat = _settings.TimeFormat;

            if (!String.IsNullOrWhiteSpace(nickNameFormat))
            {
                if (!String.IsNullOrWhiteSpace(timeFormat))
                {
                    return nickNameFormat
                        .Replace("{time}", message.ReceiveDate.ToString(timeFormat))
                        .Replace("{nick}", message.NickName);
                }
                else
                {
                    return nickNameFormat
                        .Replace("{time}", "")
                        .Replace("{nick}", message.NickName);
                }
            }

            return String.Empty;
        }

        private string FormatMessage(IrcManagerMessage message)
        {
            string messageFormat = _settings.MessageFormat;
            if (!String.IsNullOrWhiteSpace(messageFormat))
                return messageFormat.Replace("{msg}", message.Message);

            return String.Empty;
        }

        private FontRenderer GetNickNameFontRenderer(IrcManagerMessageSource source)
        {
            switch (source)
            { 
                case IrcManagerMessageSource.Broadcaster:
                    return _broadcasterNicknameFontRenderer;

                case IrcManagerMessageSource.Moderator:
                    return _moderatorNicknameFontRenderer;

                case IrcManagerMessageSource.Friend:
                    return _friendNicknameFontRenderer;

                case IrcManagerMessageSource.Foe:
                    return _foeNicknameFontRenderer;

                default:
                    return _userNicknameFontRenderer;
            }
        }

        private FontRenderer GetMessageFontRenderer(IrcManagerMessageSource source)
        {
            switch (source)
            {
                case IrcManagerMessageSource.Broadcaster:
                    return _broadcasterMessageFontRenderer;

                case IrcManagerMessageSource.Moderator:
                    return _moderatorMessageFontRenderer;

                case IrcManagerMessageSource.Friend:
                    return _friendMessageFontRenderer;

                case IrcManagerMessageSource.Foe:
                    return _foeMessageFontRenderer;

                default:
                    return _userMessageFontRenderer;
            }
        }

        public void Update(float elapsedSeconds)
        {
            _emoteManager.Update(elapsedSeconds);
        }

        public void Draw(int width, int height)
        {
            lock (_lock)
            {
                Resize(width, height);

                _graphics.Clear(_clearColor);

                // recalculate positions
                int lineNumber = 0;
                float lineWidth = 0f;
                bool lastNewLine = false;
                DrawableBase drawable;
                for (int i = 0; i < _drawableList.Count; i++)
                {
                    drawable = _drawableList[i];
                    drawable.LineNumber = lineNumber;

                    lineWidth += drawable.Width + _spaceWidth;
                    if (lineWidth > width || lastNewLine)
                    {
                        lineNumber += 1;
                        lineWidth = drawable.Width + _spaceWidth;

                        drawable.LineNumber = lineNumber;
                    }

                    lastNewLine = drawable.NewLine;
                }

                int lastLineNumber = int.MaxValue;
                float positionY = height - _textHeight - _textHeight * ((float)lineNumber + 1);
                float positionX = 0f;
                for (int i = 0; i < _drawableList.Count; i++)
                {
                    drawable = _drawableList[i];
                    if (drawable.LineNumber != lastLineNumber)
                    {
                        positionY += _textHeight;
                        positionX = 0f;
                    }

                    drawable.Y = positionY;
                    drawable.X = positionX;

                    positionX += drawable.Width + _spaceWidth;

                    drawable.Draw(_graphics);

                    lastLineNumber = drawable.LineNumber;
                }

                _writeTexture = true;
            }
        }

        public void WriteTexture()
        {
            lock (_lock)
            {
                _bitmapData = _bitmap.LockBits(_fullBitmapRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                Marshal.Copy(_bitmapData.Scan0, _bitmapBytes, 0, _bitmapData.Stride * _bitmapData.Height);
                _bitmap.UnlockBits(_bitmapData);

                _texture.SetImage(_bitmapBytes, GSImageFormat.GS_IMAGEFORMAT_BGRA, (uint)_bitmapData.Stride);
                _writeTexture = false;
            }
        }

        public void Dispose()
        {
            if (_texture != null)
            {
                _texture.Dispose();
                _texture = null;
            }

            if (_emoteManager != null)
            {
                _emoteManager.Dispose();
                _emoteManager = null;
            }

            if (_universalFontRenderer != null)
            {
                _universalFontRenderer.Dispose();
                _universalFontRenderer = null;
            }

            if (_broadcasterNicknameFontRenderer != null)
            {
                _broadcasterNicknameFontRenderer.Dispose();
                _broadcasterNicknameFontRenderer = null;
            }

            if (_broadcasterMessageFontRenderer != null)
            {
                _broadcasterMessageFontRenderer.Dispose();
                _broadcasterMessageFontRenderer = null;
            }

            if (_moderatorNicknameFontRenderer != null)
            {
                _moderatorNicknameFontRenderer.Dispose();
                _moderatorNicknameFontRenderer = null;
            }

            if (_moderatorMessageFontRenderer != null)
            {
                _moderatorMessageFontRenderer.Dispose();
                _moderatorMessageFontRenderer = null;
            }

            if (_friendNicknameFontRenderer != null)
            {
                _friendNicknameFontRenderer.Dispose();
                _friendNicknameFontRenderer = null;
            }

            if (_friendMessageFontRenderer != null)
            {
                _friendMessageFontRenderer.Dispose();
                _friendMessageFontRenderer = null;
            }

            if (_foeNicknameFontRenderer != null)
            {
                _foeNicknameFontRenderer.Dispose();
                _foeNicknameFontRenderer = null;
            }

            if (_foeMessageFontRenderer != null)
            {
                _foeMessageFontRenderer.Dispose();
                _foeMessageFontRenderer = null;
            }

            if (_userNicknameFontRenderer != null)
            {
                _userNicknameFontRenderer.Dispose();
                _userNicknameFontRenderer = null;
            }

            if (_userMessageFontRenderer != null)
            {
                _userMessageFontRenderer.Dispose();
                _userMessageFontRenderer = null;
            }

            if (_graphics != null)
            {
                _graphics.Dispose();
                _graphics = null;
            }

            if (_bitmap != null)
            {
                _bitmap.Dispose();
                _bitmap = null;
            }

            if (_drawableList != null && _drawableList.Count > 0)
            {
                for (int i = 0; i < _drawableList.Count; i++)
                {
                    if (_drawableList[i] != null)
                        _drawableList[i].Dispose();
                }
            }
        }
    }
}
